using System;
using System.Diagnostics;
using System.Globalization;

namespace Pegasus
{
	/// <summary>
	/// Abstract class used to provide base implementation for the reoccurring date time classes.
	/// </summary>
	[ Serializable ]
	public abstract class DateTimeReoccurring
	{
		// Local Instance Values
		private DateTime m_startDate = DateTime.Today;
		private DateTime m_runTime = DateTime.Now;

		/// <summary>
		/// Default Constructor
		/// </summary>
		protected DateTimeReoccurring()
		{
		}

		/// <summary>
		/// Get/Set the start date of when the reoccurring pattern starts.
		/// </summary>
		public DateTime StartDate
		{
			get
			{
				return m_startDate;
			}

			set
			{
				m_startDate = value;
			}
		}

		/// <summary>
		/// Get/Set the time of day (date part of the DateTime object is ignored) to
		/// run the task at.
		/// </summary>
		public DateTime RunTime
		{
			get
			{
				return m_runTime;
			}

			set
			{
				m_runTime = value;
			}
		}

		/// <summary>
		/// Get the date and time of the occurrance of the pattern from the
		/// current system date time.
		/// </summary>
		/// <returns>A <see cref="DateTime"/> of the next occurance.</returns>
		public virtual DateTime GetNextOccurrence()
		{
			return GetNextOccurrence( DateTime.Now );
		}

		/// <summary>
		/// Get the date and time of the occurrance of the pattern from the
		/// given date time.
		/// </summary>
		/// <param name="fromDate">The date to calculate the pattern from.</param>
		/// <returns>A <see cref="DateTime"/> of the next occurance.</returns>
		public abstract DateTime GetNextOccurrence( DateTime fromDate );

		/// <summary>
		/// Get the text description for the occurrence.
		/// </summary>
		/// <returns></returns>
		public abstract string GetDisplayText();

		/// <summary>
		/// Gets the days in month.
		/// </summary>
		/// <param name="date">The date object to use the year and month from.</param>
		/// <returns>The number of days in the month</returns>
		protected int GetDaysInMonth( DateTime date )
		{
			return DateTimeFormatInfo.CurrentInfo.Calendar.GetDaysInMonth( date.Year, date.Month );
		}


		/// <summary>
		/// Gets the day of the week based on the which day and date of the week.
		/// </summary>
		/// <param name="whichDay">The which day.</param>
		/// <param name="dayOfWeek">The day of week.</param>
		/// <param name="firstOfTheMonth">The first of the month.</param>
		/// <returns></returns>
		protected DateTime GetDayOfWeek( WhichDay whichDay, int dayOfWeek, DateTime firstOfTheMonth )
		{
			DateTime ret = firstOfTheMonth;

			if( whichDay != WhichDay.Last )
			{
				ret = GetDayOfWeekFirst( (int) whichDay, dayOfWeek, firstOfTheMonth );
			}
			else
			{
				ret = GetDayOfWeekLast( dayOfWeek, firstOfTheMonth );
			}

			return ret;
		}

		/// <summary>
		/// Gets the day of the of the month using the offset and day of the week.
		/// </summary>
		/// <param name="offset">The week offset first week, second week, etc...</param>
		/// <param name="dayOfWeek">The day of week.</param>
		/// <param name="firstOfTheMonth">The first of the month.</param>
		/// <returns></returns>
		private DateTime GetDayOfWeekFirst( int offset, int dayOfWeek, DateTime firstOfTheMonth )
		{
			DateTime ret = firstOfTheMonth;

			// If we are looking for the first, second, etc... day of the month then just drop
			// out and return the first of the month plus offset
			if( dayOfWeek != 7 )
			{
				// Move the base date to the number of weeks from the first of the month
				DateTime baseDate = firstOfTheMonth.AddDays( offset * 7 );

				int baseDayOfWeek = (int) baseDate.DayOfWeek;

				// Is the base day less than the day we are looking for.  For example
				// we are looking for a Friday and the base day is a Monday.
				if( baseDayOfWeek < dayOfWeek )
				{
					// Just add the different to the base date.
					ret = baseDate.AddDays( dayOfWeek - baseDayOfWeek );
				}
				else if( baseDayOfWeek > dayOfWeek )
				{
					// Is the base day is greater than the day we are looking for.
					// For example we are looking for a Monday and the base is a
					// Friday.

					// Take the different between the two days, minus the number of days
					// in a week give us the number of days to add to the base date
					// to get to the day we want.
					ret = baseDate.AddDays( 7 - ( baseDayOfWeek - dayOfWeek ) );

				}
				else
				{
					// The base day is the same was we are looking for.
					// so just drop out and return the base date
					ret = baseDate;
				}
			}
			else
			{
				ret = firstOfTheMonth.AddDays( offset );
			}

			return ret;
		}

		/// <summary>
		/// Gets the day last.
		/// </summary>
		/// <param name="dayOfWeek">The day of week.</param>
		/// <param name="firstOfTheMonth">The first of the month.</param>
		/// <returns></returns>
		private DateTime GetDayOfWeekLast( int dayOfWeek, DateTime firstOfTheMonth )
		{
			// Get the last day of the month
			int daysInMonth = GetDaysInMonth( firstOfTheMonth );
			DateTime lastOfTheMonth = firstOfTheMonth.AddDays( daysInMonth - 1 );

			DateTime ret = lastOfTheMonth;

			// If we are looking for the last day of the month, then return 
			// the last day of the month.
			if( dayOfWeek != 7 )
			{
				int lastDayOfWeek = (int) lastOfTheMonth.DayOfWeek;

				// Is the last day less than the day we are looking for.  For example
				// we are looking for a Friday and the last day is a Monday.
				if( lastDayOfWeek < dayOfWeek )
				{
					// Take the different between the two days, minus the number of days
					// in a week give us the number of days to subtract from the last date
					// to get to the day we want.
					ret = lastOfTheMonth.AddDays( -1 * ( 7 - ( dayOfWeek - lastDayOfWeek ) ) );

				}
				else if( lastDayOfWeek > dayOfWeek )
				{
					// Is the base day is greater than the day we are looking for.
					// For example we are looking for a Monday and the last is a
					// Friday.

					// Just subtract the different from the last date.
					ret = lastOfTheMonth.AddDays( -1 * ( lastDayOfWeek - dayOfWeek ) );
				}
				else
				{
					// The last day is the same was we are looking for.
					// so just drop out and return the last date
				}
			}

			return ret;
		}

		/// <summary>
		/// 
		/// </summary>
		public enum WhichDay
		{
			/// <summary></summary>
			First,

			/// <summary></summary>
			Second,

			/// <summary></summary>
			Third,

			/// <summary></summary>
			Fourth,

			/// <summary></summary>
			Last,
		}
	}
}
